use std::ffi::c_void;
use std::sync::Mutex;

use jni::objects::{GlobalRef, JObject, JValue,JThrowable};
use jni::sys::*;
use jni::JNIEnv;
use jni::{JavaVM, NativeMethod};
use tracing::info;
 

pub static JVM_GLOBAL: std::sync::OnceLock<Mutex<Option<JavaVM>>> = std::sync::OnceLock::new();
pub static JNI_CALLBACK: std::sync::OnceLock<Mutex<Option<GlobalRef>>> = std::sync::OnceLock::new();
// 添加一个全局变量来缓存回调对象

pub fn make_exception<'local>(
    env: &mut JNIEnv<'local>,
    err: &Error,
) -> jni::errors::Result<JThrowable<'local>> {
    let exception = env.find_class("java/lang/RuntimeException")?;
    let err = env.new_string(format!("{:?}", err))?;
    env.new_object(exception, "(Ljava/lang/String;)V", &[JValue::Object(&err)])
        .map(JThrowable::from)
}

macro_rules! jni_method {
    ( $method:tt, $signature:expr ) => {{
        jni::NativeMethod {
            name: jni::strings::JNIString::from(stringify!($method)),
            sig: jni::strings::JNIString::from($signature),
            fn_ptr: $method as *mut c_void,
        }
    }};
}

pub fn start_dll_server(_env: JNIEnv, _obj: jobject) {}

/// 动态库被 java 加载时 会出发此函数, 在此动态注册本地方法
#[no_mangle]
#[allow(non_snake_case)]
pub fn JNI_OnLoad(jvm: JavaVM, _reserved: *mut c_void) -> jint {
    let class_name: &str = "Test";
    let jni_methods = [
        jni_method!(native_set_callback, "(LTest$RCallback;)V"),
        jni_method!(start_dll_server, "()V"),
    ];

    let ok = register_natives(&jvm, class_name, jni_methods.as_ref());

    // 在动态库被java加载时, 缓存一个jvm到全局变量, 调用回调时使用
    let _ = JVM_GLOBAL.set(Mutex::new(Some(jvm)));
    ok
}

#[allow(clippy::not_unsafe_ptr_arg_deref)]
#[no_mangle]
pub fn native_set_callback(env: JNIEnv, _obj: jobject, callback: jobject) {
    // 创建一个全局引用,

    let callback = env
        .new_global_ref(unsafe { JObject::from_raw(callback) })
        .unwrap();

    // 添加到全局缓存
    let jni_callback_ret = JNI_CALLBACK.set(Mutex::new(Some(callback)));
    log::debug!("jni callback ret : {:?}", jni_callback_ret);
    //  let mut ptr_fn = JNI_CALLBACK.lock().unwrap();
    // *ptr_fn = Some(callback);
}

/// 回调 Test 对象的 { void onStateChanged(int state) } 函数
pub fn call_java_callback(state: String) {
    let jni_callback = JNI_CALLBACK.get().unwrap();
    call_jvm(jni_callback, move |obj: &JObject, env: &mut JNIEnv| {
        let message = env.new_string(&state).unwrap();

        let args: &[JValue] = &[(&message).into()];
        match env.call_method(obj, "onStateChanged", "(Ljava/lang/String;)V", args) {
            Ok(jvalue) => {
                log::debug!("callback succeed: {:?}", jvalue);
            }
            Err(e) => {
                log::debug!("callback failed : {:?}", e);
            }
        }
    });
}

///
/// # 封装jvm调用
pub fn call_jvm<F>(callback: &Mutex<Option<GlobalRef>>, run: F)
where
    F: Fn(&JObject, &mut JNIEnv) + Send + 'static,
{
    let jvm_global = JVM_GLOBAL.get().unwrap();

    let ptr_jvm = jvm_global.lock().unwrap();
    if (*ptr_jvm).is_none() {
        return;
    }
    let ptr_fn = callback.lock().unwrap();
    if (*ptr_fn).is_none() {
        return;
    }
    let jvm: &JavaVM = (*ptr_jvm).as_ref().unwrap();

    match jvm.attach_current_thread_permanently() {
        Ok(mut env) => {
            let obj = (*ptr_fn).as_ref().unwrap().as_obj();
            run(obj, &mut env);

            // 检查回调是否发生异常, 如果有异常发生,则打印并清空
            if let Ok(true) = env.exception_check() {
                let a = env.exception_describe();
                log::debug!("a = {:?}", a);
                let b = env.exception_clear();
                log::debug!("b = {:?}", b);
                let c = env.throw_new("java/lang/Exception", "JNI抛出的异常！");
                log::debug!("c = {:?}", c);
            }
        }
        Err(e) => {
            log::debug!("jvm attach_current_thread failed: {:?}", e);
        }
    }
}

pub fn register_natives(jvm: &JavaVM, class_name: &str, methods: &[NativeMethod]) -> jint {
    let mut env: JNIEnv = jvm.get_env().unwrap();
    let jni_version = env.get_version().unwrap();
    let version: jint = jni_version.into();

    log::debug!("JNI Version : {:#?} ", jni_version);

    let clazz = match env.find_class(class_name) {
        Ok(clazz) => clazz,
        Err(e) => {
            log::debug!("java class not found : {:?}", e);
            return JNI_ERR;
        }
    };
    let result = env.register_native_methods(clazz, methods);

    if result.is_ok() {
        log::debug!("register_natives : succeed");
        version
    } else {
        log::debug!("register_natives : failed ");
        JNI_ERR
    }
}
